//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using Microsoft.Research.ScientificWorkflow;
namespace Microsoft.Research.ScientificWorkflow.TridentModel
{
    #region Using Directives
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.IO;
    using SD = System.Drawing;
    using WM = System.Windows.Media;
    using System.Collections.ObjectModel;
    using System.Workflow.ComponentModel;

    using ER = Microsoft.Research.DataLayer;
    using Microsoft.Research.eResearch.Common.Linq;
    using Type = System.Type;
    using Activity = System.Workflow.ComponentModel.Activity;
    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using System.Drawing;
    using System.Workflow.Activities;
    using System.Windows;
    using System.Collections.Specialized;
    using Microsoft.Research.DataLayer;
    #endregion

    [Serializable()]
    public class CompositeActivityModel : BaseCompositeModel
    {
        #region Private Member Data

        /// <summary>
        /// Whether BuiltIn Model.
        /// </summary>
        private bool isBuiltIn;

        /// <summary>
        /// This is used to check if teh Activity Parameter Bindings are visible or not.
        /// </summary>
        private Visibility currentParameterBindingVisibility = Visibility.Visible;

        /// <summary>
        /// The execution status of the activity.
        /// </summary>
        private TridentActivityExecutionStatus currentTridentActivityExecutionStatus = TridentActivityExecutionStatus.None;

        #endregion

        #region Constructors

        /// <summary>
        /// Constructor of Activity Model.
        /// </summary>
        /// <param name="erActivity">Activity for which the model has to be created.</param>
        public CompositeActivityModel(ER.ActivitySequence erActivitySequence, ModelFactory modelFactory)
            : this(erActivitySequence, null, modelFactory) { }

        /// <summary>
        /// Constructor of Activity Model.
        /// </summary>
        /// <param name="source">Activity metadata for the activity model.</param>
        public CompositeActivityModel(ActivityMetadata source, string uniqueCount)
        {
            #region Validation of Arguments
            if (string.IsNullOrEmpty(uniqueCount))
            {
                throw new ArgumentNullException("uniqueCount");
            }

            if (null == source)
            {
                throw new ArgumentNullException("source");
            }
            #endregion

            if (source != null)
            {
                // Set the Registry ID of the Activity.
                this.Id = source.Id;

                // Set the UniqueID.
                this.UniqueId = source.Name + "_" + uniqueCount;

                // Set Class of the activity.
                this.ActivityClass = source.ActivityClass;

                // Set Name of the activity.
                this.Name = source.Name;

                // Set Description of the activity.
                this.Description = source.Description;

                // Set Label of the activity.
                this.Label = source.Label;

                // Set whether the activity is BuiltIn.
                this.isBuiltIn = source.IsBuiltIn;
                this.Children.CollectionChanged += new NotifyCollectionChangedEventHandler(this.OnChildCollectionChanged);
                this.ChildrenCollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(this.OnChildCollectionChanged);

                // Indicate if a newer version of this activity is available.
                this.UpdateAvailable = source.UpdateAvailable;

                // Get the latest entity id.
                this.LatestEntityId = source.LatestEntityId;

                // Set the version.
                this.Version = source.Version;

                // Set whether the activity is Condition Activity.
                this.IsConditionActivity = source.IsConditionActivity;

                // Set the display label of Condition Activity.
                this.DisplayLabel = source.DisplayLabel;

                this.AssociatedAssemblies = source.AssociatedAssemblies;
                this.AssociatedAssemblies.ForEach(assembly => assembly.HolderModel = this);

                // Set the input and output parameters.
                foreach (ParameterDescriptionModel metadata in source.InputParameters)
                {
                    this.InputParameters.Add(new ParameterDescriptionModel(metadata));
                }

                foreach (ParameterDescriptionModel metadata in source.OutputParameters)
                {
                    this.OutputParameters.Add(new ParameterDescriptionModel(metadata));
                }
            }
        }

        /// <summary>
        /// Create the activity model for the Activity object.
        /// </summary>
        /// <param name="activity">
        /// The ComponentModel.Activity object.
        /// </param>
        public CompositeActivityModel(Activity activity, ModelFactory modelFactory) : this(activity, null, modelFactory) { }

        /// <summary>
        /// Create ActivityModel recursively.
        /// </summary>
        /// <param name="activity">
        /// Activity for which to create the activity model.
        /// </param>
        /// <param name="parentModel">
        /// The parent model for the activity.
        /// </param>
        public CompositeActivityModel(Activity activity, BaseCompositeModel parentModel, ModelFactory modelFactory)
        {
            if (activity != null)
            {
                this.GetDetailsFromActivity(activity, parentModel, modelFactory);
            }
        }

        /// <summary>
        ///  Constructor of Activity Model.
        /// </summary>
        /// <param name="erActivitySequence">Activity for which the model has to be created.</param>
        /// <param name="parentModel">Parent model of teh Activity Sequence.</param>
        public CompositeActivityModel(ER.ActivitySequence erActivitySequence, BaseCompositeModel parentModel, ModelFactory modelFactory)
        {
            #region Validation of Arguments
            if (null == erActivitySequence)
            {
                throw new ArgumentNullException("erActivitySequence");
            }
            #endregion

            // Create the Activity Model.
            this.CreateActivityModel(
                erActivitySequence,
                parentModel,
                modelFactory,
                erActivitySequence.Name);

            this.UpdateParameterAssignments(erActivitySequence);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CompositeActivityModel"/> class.
        /// </summary>
        /// <param name="updatedActivity">The updated activity.</param>
        /// <param name="parentModel">The parent model.</param>
        /// <param name="uniqueCount">The unique count.</param>
        public CompositeActivityModel(ER.Activity updatedActivity, BaseCompositeModel parentModel, string uniqueCount)
        {
            #region Validation of Arguments
            if (null == updatedActivity)
            {
                throw new ArgumentNullException("updatedActivity");
            }
            #endregion

            // Create the Activity Model.
            this.CreateActivityModel(
                updatedActivity,
                parentModel,
                updatedActivity.Name + "_" + uniqueCount);
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="name">Name of the activity.</param>
        /// <param name="description">Description of the activity.</param>
        /// <param name="label">label of the activity.</param>
        /// <param name="uniqueName">Uniquename of the activity.</param>
        /// <param name="inputParameters">List of input parameters of type <see cref="Microsoft.Research.ScientificWorkflow.TridentModel.ParameterDescriptionModel"/>.</param>
        /// <param name="outputParameters">List of output parameters of type <see cref="Microsoft.Research.ScientificWorkflow.TridentModel.ParameterDescriptionModel"/>.</param>
        public CompositeActivityModel(
            string name,
            string description,
            string label,
            string uniqueName,
            Collection<ParameterDescriptionModel> inputParameters,
            Collection<ParameterDescriptionModel> outputParameters)
        {
            #region Validation of Arguments
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            if (string.IsNullOrEmpty(uniqueName))
            {
                throw new ArgumentNullException("uniqueName");
            }

            if (null == inputParameters)
            {
                throw new ArgumentNullException("inputParameters");
            }

            if (null == outputParameters)
            {
                throw new ArgumentNullException("outputParameters");
            }
            #endregion

            // Set Name of the activity.
            this.Name = name;

            // Set Description of the activity.
            this.Description = description;

            // Set Label of the activity.
            this.Label = label;

            // Set the UniqueID.
            this.UniqueId = uniqueName;

            // Set the input parameters.
            foreach (ParameterDescriptionModel inputParam in inputParameters)
            {
                this.InputParameters.Add(inputParam);
            }

            // Set the output parameters.
            foreach (ParameterDescriptionModel outputParam in outputParameters)
            {
                this.OutputParameters.Add(outputParam);
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets whether the Activity is of Built In Type.
        /// </summary>
        public bool IsBuiltIn
        {
            get
            {
                return this.isBuiltIn;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is condition activity.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is condition activity; otherwise, <c>false</c>.
        /// </value>
        public bool IsConditionActivity { get; private set; }

        /// <summary>
        /// Gets or sets the display label.
        /// </summary>
        /// <value>The display label.</value>
        public string DisplayLabel { get; private set; }

        /// <summary>
        /// Gets and sets the CurrentActivityExecutionStatus.
        /// </summary>
        public TridentActivityExecutionStatus CurrentTridentActivityExecutionStatus
        {
            get
            {
                return this.currentTridentActivityExecutionStatus;
            }

            set
            {
                if (value != this.currentTridentActivityExecutionStatus)
                {
                    this.currentTridentActivityExecutionStatus = value;
                    this.RaisePropertyChanged("CurrentTridentActivityExecutionStatus");
                }
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a simple activity model.
        /// </summary>
        public override bool IsSimpleActivityModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a composite activity model.
        /// </summary> 
        public override bool IsCompositeActivityModel
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a subsection model.
        /// </summary>
        public override bool IsSubsectionModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a For model.
        /// </summary>
        public override bool IsForActivityModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a replicator model.
        /// </summary>
        public override bool IsReplicatorActivityModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is an ifElse model.
        /// </summary>
        public override bool IsIfElseActivityModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a simple blackbox model.
        /// </summary>
        public override bool IsSimpleBlackBoxModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets true if the concrete implementation is a composite blackbox model.
        /// </summary>
        public override bool IsCompositeBlackBoxModel
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets or sets the visibility of teh Activity Bindings.
        /// </summary>
        public override Visibility CurrentParameterBindingVisibility
        {
            get
            {
                return this.currentParameterBindingVisibility;
            }
            set
            {
                this.currentParameterBindingVisibility = value;
            }
        }

        /// <summary>
        /// If false, the start and end elements are inserted, if true they are not inserted.
        /// </summary>
        public bool HideStartEnd { get; set; }

        /// <summary>
        /// Gets or sets the version.
        /// </summary>
        /// <value>The version.</value>
        public override int Version
        {
            get;
            set;
        }

        public override bool UpdateAvailable
        {
            get
            {
                bool updateAvailable = true;
                if ((this.ParentModel != null && this.ParentModel.IsIfElseActivityModel)
                    || (this.ActivityClass.Equals("TridentBasicActivities.Activities.IfTrue")
                    || this.ActivityClass.Equals("TridentBasicActivities.Activities.IfFalse")))
                {
                    updateAvailable = false;
                }
                else
                {
                    updateAvailable = base.UpdateAvailable;
                }

                return updateAvailable;
            }
            set
            {
                base.UpdateAvailable = value;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Using the BaseVisitor, perform the operation.
        /// </summary>
        /// <param name="visitor">
        /// The implementation of visitor.
        /// </param>
        public override void Accept(TridentBaseVisitor visitor)
        {
            visitor.VisitCompositeActivityModel(this);
        }

        #endregion Public Methods

        #region Private Methods

        /// <summary>
        /// This function is used to create the Activity Model.
        /// </summary>
        /// <param name="erActivitySequence">Activity Sequence of the Activity for which the Activity Model has to be created.</param>
        /// <param name="parentActivity">Parent Activity Model.</param>
        /// <param name="uniqueName">Unique ID of the Activity.</param>
        /// <returns>Activty Model.</returns>
        private CompositeActivityModel CreateActivityModel(
            ER.ActivitySequence erActivitySequence,
            BaseCompositeModel parentActivity,
            ModelFactory modelFactory,
            string uniqueName)
        {
            ER.Activity erActivity = erActivitySequence.Activity;
            this.CreateActivityModel(erActivity, parentActivity, uniqueName);

            for (int index = 0; index < erActivitySequence.Children.Count; index++)
            {
                ER.ActivitySequence childActivity = erActivitySequence.Children[index];
                if (childActivity != null)
                {
                    BaseModel childModel = modelFactory.FetchModelByActivitySequence(childActivity, this);
                    if (null != childModel)
                    {
                        // Add childActivity Model to the parent.
                        this.Children.Add(childModel);
                    }
                }
            }

            return this;
        }

        /// <summary>
        /// Creates the activity model.
        /// </summary>
        /// <param name="erActivity">The er activity.</param>
        /// <param name="parentActivity">The parent activity.</param>
        /// <param name="uniqueName">Name of the unique.</param>
        /// <returns></returns>
        private CompositeActivityModel CreateActivityModel(
            ER.Activity erActivity,
            BaseCompositeModel parentActivity,
            string uniqueName)
        {
            // Set the Registry ID of the Activity.
            this.Id = ((ER.IObject)erActivity).ID;

            // Set the UniqueID.
            this.UniqueId = uniqueName;

            // Set Class of the activity.
            this.ActivityClass = erActivity.ActivityClass;

            // Set Name of the activity.
            this.Name = erActivity.Name;

            // Set Description of the activity.
            this.Description = erActivity.Description;

            // Set Label of the activity.
            this.Label = erActivity.Label;

            // Set whether the activity is BuiltIn.
            this.isBuiltIn = erActivity.IsBuiltIn;

            // Set whether the activity is Deleted.
            this.IsDeleted = erActivity.IsDeleted;

            // Check if an update is available.
            ER.Activity latestActivity = BaseModel.CheckIfUpdateAvailable(erActivity);
            if (latestActivity != null)
            {
                this.UpdateAvailable = true;
                this.LatestEntityId = (latestActivity as ER.IObject).ID;
            }

            this.Version = erActivity.Version;

            // Set the Parent of this activity.
            if (null != parentActivity)
            {
                this.ParentModel = parentActivity;
            }

            // Set whether the activity is Condition Activity.
            this.IsConditionActivity = erActivity.IsCondition;

            // Set the display label of Condition Activity.
            this.DisplayLabel = erActivity.DisplayLabel;

            this.Children.CollectionChanged += new NotifyCollectionChangedEventHandler(this.OnChildCollectionChanged);
            this.ChildrenCollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(this.OnChildCollectionChanged);

            this.PopulateAssociatedAssemblies(erActivity);

            // Initialize Parameters.
            this.InitParamModels(erActivity);
            return this;
        }

        /// <summary>
        /// Initialze the activity model, recursively, using the Activity object.
        /// </summary>
        /// <param name="activity">
        /// ComponentModel.Activity object.
        /// </param>
        /// <param name="parentActivityModel">
        /// The parent activity model.
        /// </param>
        /// <returns>
        /// Activitymodel representation for the ComponentModel.Activity passed.
        /// </returns>
        private CompositeActivityModel GetDetailsFromActivity(Activity activity, BaseCompositeModel parentActivityModel, ModelFactory modelFactory)
        {
            Type activityType = activity.GetType();
            this.UniqueId = activity.Name;
            this.ActivityClass = activityType.FullName;
            this.Id = Guid.Empty;

            object[] attsNameLabel = null;
            try
            {
                attsNameLabel = activityType.GetCustomAttributes(typeof(NameAttribute), false);
            }
            catch (TypeLoadException ex)
            {
                Logger.Write(ex.Message);
            }

            this.Name = activityType.Name;
            if ((attsNameLabel == null) || ((attsNameLabel != null) && (attsNameLabel.Length == 0)))
            {
                this.Label = activityType.Name;

                // Set the display label of Condition Activity.
                this.DisplayLabel = activityType.Name;
            }
            else
            {
                this.Label = ((NameAttribute)attsNameLabel[0]).Label;

                // Set the display label of Condition Activity.
                this.DisplayLabel = ((NameAttribute)attsNameLabel[0]).DisplayLabel;
            }

            object[] attsDesc = null;
            try
            {
                attsDesc = activityType.GetCustomAttributes(typeof(DescriptionAttribute), false);
            }
            catch (TypeLoadException ex)
            {
                Logger.Write(ex.Message);
            }

            if (attsDesc != null && attsDesc.Length > 0)
            {
                this.Description = ((DescriptionAttribute)attsDesc[0]).Description;
            }

            this.IsDeleted = false;
            if (parentActivityModel != null)
            {
                this.ParentModel = parentActivityModel;
            }

            if (activityType.Namespace.Equals("System.Workflow.Activities") || activityType.Namespace.Equals("System.Workflow.ComponentModel"))
            {
                this.isBuiltIn = true;
            }
            else
            {
                this.isBuiltIn = false;
            }

            // If the activity is of type IConditionActivity then get the Display label and the set IsCondition to true.
            IConditionActivity condActivity = activity as IConditionActivity;
            if (condActivity != null)
            {
                // Set whether the activity is Condition Activity.
                this.IsConditionActivity = true;
            }

            CompositeActivity compAct = activity as CompositeActivity;
            if (compAct != null)
            {
                this.Children.CollectionChanged += new NotifyCollectionChangedEventHandler(this.OnChildCollectionChanged);
                this.ChildrenCollectionChanged += new EventHandler<NotifyCollectionChangedEventArgs>(this.OnChildCollectionChanged);

                for (int index = 0; index < compAct.Activities.Count; index++)
                {
                    Activity childActivity = compAct.Activities[index];
                    BaseModel childModel = modelFactory.FetchModelByActivity(childActivity, this);
                    if (null != childModel)
                    {
                        // Add childActivity Model to the parent.
                        this.Children.Add(childModel);
                    }
                }
            }

            this.InitParamModels(activity, activityType);
            return this;
        }

        /// <summary>
        /// Update the parameter assignments for the activity model.
        /// </summary>
        /// <param name="activitySequence"></param>
        public void UpdateParameterAssignments(ER.ActivitySequence activitySequence)
        {
            // Iterate through the parameter assignments of the activity sequence.
            foreach (ER.ParameterAssignment parameterAssignment in activitySequence.ParameterAssignments)
            {
                // Retreive the target parameter.
                ParameterDescriptionModel paramModel = this.GetParameterDescriptionModel(parameterAssignment.ActivityParameter);
                if (paramModel != null)
                {
                    // If the parameter is databound then set the bindings.
                    if (parameterAssignment.IsBound)
                    {
                        paramModel.UpdateBindings(parameterAssignment);
                    }
                    else
                    {
                        // else update the value.
                        paramModel.UpdateValue(parameterAssignment);
                    }
                }
            }
        }

        /// <summary>
        /// Return the parameter model based on the parameter name.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns>The parameter model.</returns>
        private ParameterDescriptionModel GetParameterDescriptionModel(ActivityParameter parameter)
        {
            if (parameter.Direction == DirectionType.Input)
            {
                // Check in the input parameters.
                foreach (ParameterDescriptionModel inputParam in this.InputParameters)
                {
                    if (inputParam.PropertyName.Equals(parameter.Name, StringComparison.CurrentCultureIgnoreCase))
                    {
                        return inputParam;
                    }
                }
            }
            else
            {
                // Check in the output parameters.
                foreach (ParameterDescriptionModel outputParam in this.OutputParameters)
                {
                    if (outputParam.PropertyName.Equals(parameter.Name, StringComparison.CurrentCultureIgnoreCase))
                    {
                        return outputParam;
                    }
                }
            }

            // Return null if parameter model not found.
            return null;
        }

        /// <summary>
        /// Initialize the input and output parameters.
        /// </summary>
        /// <param name="activity">
        /// ComponentModel.Activity.
        /// </param>
        /// <param name="activityType">
        /// The type of the activity.
        /// </param>
        private void InitParamModels(Activity activity, Type activityType)
        {
            PropertyInfo[] props = activityType.GetProperties();
            foreach (PropertyInfo prop in props)
            {
                try
                {
                    object[] inAtts = prop.GetCustomAttributes(typeof(InputParamAttribute), true);
                    if (inAtts.Length > 0)
                    {
                        this.InputParameters.Add(new ParameterDescriptionModel(prop, activity, ParamType.InputParam, (inAtts[0] as InputParamAttribute).IsRequired));
                    }

                    object[] outAtts = prop.GetCustomAttributes(typeof(OutputParamAttribute), true);
                    if (outAtts.Length > 0)
                    {
                        this.OutputParameters.Add(new ParameterDescriptionModel(prop, activity, ParamType.OutputParam, false));
                    }

                    object[] inOutAtts = prop.GetCustomAttributes(typeof(InOutParamAttribute), true);
                    if (inOutAtts.Length > 0)
                    {
                        this.InputParameters.Add(new ParameterDescriptionModel(prop, activity, ParamType.InputParam, (inOutAtts[0] as InOutParamAttribute).IsRequired));
                        this.OutputParameters.Add(new ParameterDescriptionModel(prop, activity, ParamType.OutputParam, false));
                    }
                }
                catch (Exception) { }
            }
        }

        /// <summary>
        /// This is used to initialize the Paramaeter description Model.
        /// </summary>
        /// <param name="erActivity">ActivitySequence for which the model has to be created.</param>
        private void InitParamModels(ER.Activity erActivity)
        {
            // Get and set all the paramModel in the Activity.
            foreach (ER.ActivityParameter param in erActivity.ActivityParameters)
            {
                ParameterDescriptionModel paramModel = new ParameterDescriptionModel(param);

                if (param.Direction == ER.DirectionType.Input)
                {
                    // Input Parameter.
                    this.InputParameters.Add(paramModel);
                }
                else
                {
                    // Output Parameter.
                    this.OutputParameters.Add(paramModel);
                }
            }
        }

        #endregion
    }
}
